package com.joysuch.wwyt.workflow.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.base.Strings;
import com.joysuch.wwyt.bp.entity.contractor.BpContractorStaff;
import com.joysuch.wwyt.bp.repository.BpContractorStaffDao;
import com.joysuch.wwyt.common.constant.Constant;
import com.joysuch.wwyt.core.entity.BaseConfig;
import com.joysuch.wwyt.core.entity.BaseUser;
import com.joysuch.wwyt.core.enums.BaseConfigCodes;
import com.joysuch.wwyt.core.exception.ApiCoreException;
import com.joysuch.wwyt.core.repository.BaseUserDao;
import com.joysuch.wwyt.core.service.BaseConfigService;
import com.joysuch.wwyt.util.RegionUtil;
import com.joysuch.wwyt.workflow.api.bean.PersonTrajectoryResp;
import com.joysuch.wwyt.workflow.api.bean.TrajectoryLastLocationResp;
import com.joysuch.wwyt.workflow.api.bean.TrajectoryPoint;
import com.joysuch.wwyt.workflow.api.service.PersonTrajectoryService;
import com.joysuch.wwyt.workflow.bean.*;
import com.joysuch.wwyt.workflow.entity.LocationVerifyRecord;
import com.joysuch.wwyt.workflow.enums.TrajectoryCalTypeEnum;
import com.joysuch.wwyt.workflow.enums.LocationVerifyStateEnum;
import com.joysuch.wwyt.workflow.repository.WorkFlowSignatureDao;
import com.joysuch.wwyt.workflow.service.WorkFlowTaskSignService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;


@Slf4j
@Service
public class WorkFlowTaskSignServiceImpl implements WorkFlowTaskSignService {

    private Logger logger = LoggerFactory.getLogger("verifySignArea");

    @Autowired
    private PersonTrajectoryService personTrajectoryService;
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private BpContractorStaffDao bpContractorStaffDao;
    @Autowired
    private BaseConfigService baseConfigService;
    @Autowired
    private WorkFlowSignatureDao workflowSignatureDao;

    @Override
    public LocateVerifyResultBean verifySignArea(WorkFlowSignBean signBean) throws ApiCoreException {
        Double rsPointLongitude = null;
        Double rsPointLatitude = null;
        Integer rsPointFloorId = null;
        LocationVerifyStateEnum verifyState;
        Date curDate = new Date();
        String requestId = IdUtil.simpleUUID();
        logger.info("requestId：{}，verifySignArea param :{}",requestId, JSONUtil.toJsonStr(signBean));
        String sn = "1".equals(signBean.getUserContractorFlag())
                ? _getContractorSn(signBean.getUserId())
                : getUserSn(signBean.getUserId());
        if (StringUtils.isNotBlank(sn)) {
            //获取最近一次点位
            TrajectoryLastLocationResp lastLocation = personTrajectoryService.getLastLocation(sn, signBean.getTaskCode(), requestId);
            if (lastLocation != null) {
                //如果最近一次点位在配置的时间内(10分钟) 则用最近一个点位的时间往前推60秒，看这段时间的点位是否在区域内
                if (curDate.getTime() - lastLocation.getTimestampMillisecond() <= getMaxTimeRange()) {
                    long lastPointTime = lastLocation.getTimestampMillisecond();
                    List<PersonTrajectoryResp> personTrajectoryList = personTrajectoryService.getPersonTrajectoryPoints(sn, lastPointTime, signBean.getTaskCode(), requestId);
                    addLastPersonTrajectory(lastLocation, personTrajectoryList);
                    PointVerifyResult verifyResult = getPointVerifyResult(personTrajectoryList, signBean.getFloorId(), signBean.getCalculationTypeEnum(), signBean.getLonArr(), signBean.getLatArr(), signBean.getRadius());
                    verifyState = verifyResult.isPointVerify() ? LocationVerifyStateEnum.POINT_IN_AREA : verifyResult.isFloorVerify() ? LocationVerifyStateEnum.POINT_OUT_AREA : LocationVerifyStateEnum.INCONSISTENT_FLOOR;
                    TrajectoryPoint lastPoint = verifyResult.getLastPoint();
                    rsPointLongitude = lastPoint.getLongitude();
                    rsPointLatitude = lastPoint.getLatitude();
                    rsPointFloorId = verifyResult.getFloorId();
                } else {
                    //最近一个点位不在最大时间范围内
                    verifyState = LocationVerifyStateEnum.LAST_POINT_NOT_IN_MAX_TIME_RANGE;
                    rsPointLongitude = lastLocation.getLongitude();
                    rsPointLatitude = lastLocation.getLatitude();
                    rsPointFloorId = convertFloorNo(lastLocation.getFloorNo());
                }
            } else {
                verifyState = LocationVerifyStateEnum.NO_LAST_POINT;
            }
        } else {
            verifyState = LocationVerifyStateEnum.SN_NOT_EXIST;
        }
        List<LocateVerifyRecordBean> recordList = saveLocationVerifyRecord(signBean, verifyState, curDate, requestId, rsPointLongitude, rsPointLatitude, rsPointFloorId);
        return LocateVerifyResultBean.builder().verifyResult(verifyState.equals(LocationVerifyStateEnum.POINT_IN_AREA)).records(recordList).build();
    }

    /**
     * 将最后一个点位加到校验列表中
     *
     * @param lastLocation
     * @param personTrajectoryList
     */
    private void addLastPersonTrajectory(TrajectoryLastLocationResp lastLocation, List<PersonTrajectoryResp> personTrajectoryList) {
        Integer lastPointFloorId = convertFloorNo(lastLocation.getFloorNo());
        PersonTrajectoryResp personTrajectory = new PersonTrajectoryResp();
        personTrajectory.setFloorId(lastPointFloorId);
        List<TrajectoryPoint> points = new ArrayList<>();
        TrajectoryPoint trajectoryPoint = new TrajectoryPoint();
        trajectoryPoint.setLongitude(lastLocation.getLongitude());
        trajectoryPoint.setLatitude(lastLocation.getLatitude());
        points.add(trajectoryPoint);
        personTrajectory.setPoints(points);
        personTrajectoryList.add(personTrajectory);
    }

    /**
     * 保存位置校验记录
     *
     * @param signBean
     * @param verifyState
     * @param currentDate
     * @param requestId
     * @param rsPointLongitude
     * @param rsPointLatitude
     * @param rsPointFloorId
     */
    private List<LocateVerifyRecordBean> saveLocationVerifyRecord(WorkFlowSignBean signBean, LocationVerifyStateEnum verifyState, Date currentDate, String requestId, Double rsPointLongitude, Double rsPointLatitude, Integer rsPointFloorId) {
        List<SignComponentBean> componentList = signBean.getComponentList();
        List<LocateVerifyRecordBean> recordList = new ArrayList<>();
        if(!CollectionUtils.isEmpty(componentList)){
            componentList.forEach(each->{
                LocationVerifyRecord signature = new LocationVerifyRecord();
                signature.setGroupId(each.getGroupId());
                signature.setVerifyTime(currentDate);
                signature.setUserId(signBean.getUserId());
                signature.setComponentName(each.getComponentName());
                signature.setTaskCode(signBean.getTaskCode());
                signature.setTaskName(signBean.getTaskName());
                signature.setVerifyState(verifyState.getValue());
                signature.setRequestId(requestId);
                signature.setLongitude(rsPointLongitude);
                signature.setLatitude(rsPointLatitude);
                signature.setFloorId(rsPointFloorId);
                signature.setComponentKey(each.getComponentKey());
                signature.setNewWorkflow(signBean.isNewWorkflow());
                signature.setUserContractorFlag("1".equals(signBean.getUserContractorFlag()) ? "1" : "0");
                workflowSignatureDao.save(signature);
                recordList.add(LocateVerifyRecordBean.builder().recordId(signature.getId()).compKey(each.getComponentKey()).build());
            });
        }
        return recordList;
    }

    /**
     * 开放平台楼层转换
     *
     * @param floorNo
     * @return
     */
    private Integer convertFloorNo(String floorNo) {
        if (floorNo.contains(Constant.FLOOR_REPLACE_STR)) {
            return Integer.parseInt(floorNo.replace(Constant.FLOOR_REPLACE_STR, Constant.EMPTY_STR));
        }
        return null;
    }

    /**
     * 获取最大时间范围
     *
     * @return
     */
    private long getMaxTimeRange() {
        //最近一次点位时间配置
        //默认10分钟
        long maxTime = 10 * 60 * 1000;
        BaseConfig timeConfig = baseConfigService.findFirstConfig(BaseConfigCodes.PERSON_TRAJECTORY_TRACING_TIME_MAX);
        if (timeConfig != null && !StringUtils.isEmpty(timeConfig.getValue())) {
            maxTime = Long.parseLong(timeConfig.getValue().trim()) * 1000;
        }
        return maxTime;
    }

    /**
     * 进行点位区域校验
     *
     * @param personTrajectoryList
     * @param fenceFloorId
     * @param calculationTypeEnum
     * @param lonArr
     * @param latArr
     * @param radius
     * @return
     */
    private PointVerifyResult getPointVerifyResult(List<PersonTrajectoryResp> personTrajectoryList, Integer fenceFloorId, TrajectoryCalTypeEnum calculationTypeEnum, Double[] lonArr, Double[] latArr, double radius) {
        PointVerifyResult pointVerifyResult = null;
        for (PersonTrajectoryResp trajectory : personTrajectoryList) {
            final Integer floorId = trajectory.getFloorId();
            final List<TrajectoryPoint> points = trajectory.getPoints();
            if (verifyFloorIsOpened()) {
                //开启楼层校验
                if (verifyFloor(fenceFloorId, floorId)) {
                    //楼层校验成功
                    pointVerifyResult = regionPointVerifyBatch(points, calculationTypeEnum, lonArr, latArr, radius);
                    pointVerifyResult.setFloorId(floorId);
                    if (pointVerifyResult.isPointVerify()) {
                        break;
                    }
                } else {
                    //楼层校验失败
                    pointVerifyResult = PointVerifyResult.builder().lastPoint(points.get(points.size() - 1)).floorId(floorId).floorVerify(false).pointVerify(false).build();
                }
            } else {
                //未开启楼层校验
                pointVerifyResult = regionPointVerifyBatch(points, calculationTypeEnum, lonArr, latArr, radius);
                pointVerifyResult.setFloorId(floorId);
                if (pointVerifyResult.isPointVerify()) {
                    break;
                }
            }

        }
        return pointVerifyResult;
    }

    /**
     * 点位批量校验
     *
     * @param points
     * @param calculationTypeEnum
     * @param lonArr
     * @param latArr
     * @param radius
     * @return
     */
    private PointVerifyResult regionPointVerifyBatch(List<TrajectoryPoint> points, TrajectoryCalTypeEnum calculationTypeEnum, Double[] lonArr, Double[] latArr, double radius) {
        boolean isSuccess = false;
        TrajectoryPoint lastPoint = null;
        if (!CollectionUtils.isEmpty(points)) {
            for (TrajectoryPoint point : points) {
                isSuccess = regionPointVerify(calculationTypeEnum, lonArr, latArr, radius, point.getLongitude(), point.getLatitude());
                lastPoint = point;
                if (isSuccess) {
                    break;
                }
            }
        }
        return PointVerifyResult.builder().lastPoint(lastPoint).floorVerify(true).pointVerify(isSuccess).build();
    }

    /**
     * 单个点位校验
     *
     * @param calculationTypeEnum
     * @param lonArr
     * @param latArr
     * @param radius
     * @param longitude
     * @param latitude
     * @return
     */
    private boolean regionPointVerify(TrajectoryCalTypeEnum calculationTypeEnum, Double[] lonArr, Double[] latArr, double radius, double longitude, double latitude) {
        switch (calculationTypeEnum) {
            case POINT:
                return RegionUtil.isInCircle(longitude, latitude, lonArr[0], latArr[0], radius);
            case AREA:
                return RegionUtil.isInPolygon(longitude, latitude, lonArr, latArr);
        }
        return false;
    }

    /**
     * 校验楼层
     *
     * @param fenceFloorId
     * @param openServiceFloorId
     * @return
     */
    private boolean verifyFloor(Integer fenceFloorId, Integer openServiceFloorId) {
        //开放平台的楼层没获取到直接忽略楼层校验
        if (openServiceFloorId == null) {
            return true;
        }
        return fenceFloorId.equals(openServiceFloorId);
    }

    /**
     * 楼层校验开启状态
     *
     * @return
     */
    private boolean verifyFloorIsOpened() {
        //楼层校验开关配置
        BaseConfig floorSwitchConfig = baseConfigService.findFirstConfig(BaseConfigCodes.PERSON_TRAJECTORY_VERIFY__FLOOR);
        if (floorSwitchConfig != null && !StringUtils.isEmpty(floorSwitchConfig.getValue())) {
            return Boolean.parseBoolean(floorSwitchConfig.getValue().trim());
        }
        return false;
    }

    /**
     * 获取用户sn号
     *
     * @param userId
     * @return
     */
    private String getUserSn(Long userId) {
        String sn = null;
        Optional<BaseUser> optionalUser = baseUserDao.findById(userId);
        if (optionalUser.isPresent()) {
            BaseUser baseUser = optionalUser.get();
            sn = baseUser.getSn();
        }
        return sn;
    }

    /**
     * 获取承包商sn号
     *
     * @param userId
     * @return
     */
    private String _getContractorSn(Long userId) {
        String sn = null;
        Optional<BpContractorStaff> contractorStaff = bpContractorStaffDao.findById(userId);
        if (contractorStaff.isPresent()) {
            BpContractorStaff staff = contractorStaff.get();
            sn = staff.getSn();
        }
        return sn;
    }

    @Override
    public String getOpenServiceToken() throws ApiCoreException {
        return personTrajectoryService.getTokenFromCache();
    }

    @Override
    public Boolean verifySignAreaBluetooth(WorkFlowSignBean signBean) {
        boolean isSuccess = false;
        Double longitude = signBean.getLocationPointLon();
        Double latitude = signBean.getLocationPointLat();
        if (longitude != null && latitude != null) {
            switch (signBean.getCalculationTypeEnum()) {
                case POINT:
                    isSuccess = RegionUtil.isInCircle(longitude, latitude, signBean.getLonArr()[0], signBean.getLatArr()[0], signBean.getRadius());
                    break;
                case AREA:
                    isSuccess = RegionUtil.isInPolygon(longitude, latitude, signBean.getLonArr(), signBean.getLatArr());
                    break;
            }
        }
        return isSuccess;
    }

    @Override
    public List<LocationVerifyRecordBean> getLocationVerifyRecordByTaskCode(String taskCode) {
        List<LocationVerifyRecordBean> beanList = new ArrayList<>();
        List<LocationVerifyRecord> list = workflowSignatureDao.findAllByTaskCode(taskCode);
        if (!CollectionUtils.isEmpty(list)) {
            for (LocationVerifyRecord record : list) {
                LocationVerifyRecordBean bean = new LocationVerifyRecordBean();
                BeanUtils.copyProperties(record, bean);
                bean.setExLevel(LocationVerifyStateEnum.find(record.getVerifyState()).getExLevel());
                if ("1".equals(record.getUserContractorFlag())) {
                    Optional<BpContractorStaff> contractorStaff = bpContractorStaffDao.findById(record.getUserId());
                    contractorStaff.ifPresent(con -> bean.setUserName(con.getName()));
                } else {
                    Optional<BaseUser> user = baseUserDao.findById(record.getUserId());
                    user.ifPresent(baseUser -> bean.setUserName(baseUser.getRealName()));
                }
                if (Strings.isNullOrEmpty(bean.getUserName())) {
                    bean.setUserName("");
                }
                beanList.add(bean);
            }
        }
        return beanList;
    }
}
